import java.awt.Point;
import java.util.Vector;



public class Mass
{
	// **************
	// Private data
	// **************

	private ROI			roi;
	private int			Mass_ID;

	//// Features
	public Feature		Area = new Feature();
	public Feature		Average_Grey = new Feature();
	public Feature		Variance_Grey = new Feature();

	public Feature Boundary_Length = new Feature();
	public Feature Sharpness = new Feature();
	public Feature thinness = new Feature();


	Vector<Point> Boundary_Pixels = new Vector<Point>();   //NEW

	//// Mass bounding box
	private int			Min_row;
	private int			Min_col;
	private int			Max_row;
	private int			Max_col;

	static final int find_length = 1;

	// **************
	// Methods
	// **************

	public Mass(ROI _roi, int mass_id)
	{
		roi = _roi;
		Mass_ID = mass_id;
		Min_row = 9999999; // is there a max-int const?
		Min_col = 9999999; // or should this be initialized to img w/h?
		Max_row = -1;
		Max_col = -1;
		test_init();
	}

	private void test_init()
	{
		Boundary_Length.value = 100;
		Area.value = 200;
		Sharpness.value = 1.0f;
		Average_Grey.value = 1.0f;
		Variance_Grey.value = 1.0f;
		thinness.value = 1.0f;
	}

	public ROI getROI()
	{
		return roi;
	}

	public int getMassID()
	{
		return Mass_ID;
	}

	public void updateBoundingBox(int x, int y)
	{
		if (x < Min_col)		{ Min_col = x; }
		else if (x > Max_col)	{ Max_col = x; }

		if (y < Min_row)		{ Min_row = y; }
		else if (y > Max_row)	{ Max_row = y; }
	}



	/**
	 *calculateBoundingBox determines the bounding box parameters
	 * of the smallest rectangle the surrounds the mass
	 */
	public void calculateBoundingBox(int[] pixelData, int w, int h, int startx, int starty)
	{
		//Scan ROI.labeled_image to find the smallest row, column
		//and largest row, columns where the label id of this.Mass_ID
		//exists
		int[] data = new int [pixelData.length];

		for (int y = 0; y < h; y++)
		{
			for (int x = 0; x < w; x++)
			{
				int dstOffset = y*w + x;
				int srcOffset = (y+starty) * w
				+ (x + startx);
				data[dstOffset] = pixelData[srcOffset];
			}
		}

	}


	/**
	 *Scan entire image (in next version only scan bounding box) and sum up
	 *pixels that in the ROI.labeled_image have the label of this.Mass_ID
	 *to measure the Area
	 */
	public float extract_Area()
	{
		int width = roi.getWidth();
		int height = roi.getHeight();
		int area = 0;
		int[] image = (roi.getLabeledImage()).allocPixels();
		(roi.getLabeledImage()).getPixels(image, 0, 0, width, height);
		//Scan ROI.labeled_image 
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				int index = y*(width) + x;
				if(Util.getRGBavg(image[index]) == this.Mass_ID)
					area++;
			}
		}

		this.Area.value = area;

		return this.Area.value;
	}



	/**
	 *Scan entire image (in next version only scan bounding box) and sum up
	 *pixels that in the ROI.labeled_image have the label of this.Mass_ID
	 *to measure the ave grey value
	 */
	public float extract_AveGrey()
	{
		int width = roi.getWidth();
		int height = roi.getHeight();
		int area = 0;
		int sum = 0;
		int[] image = (roi.getLabeledImage()).allocPixels();
		(roi.getLabeledImage()).getPixels(image, 0, 0, width, height);
		int[] orig_image = (roi.getOriginalImage()).allocPixels();
		(roi.getOriginalImage()).getPixels(orig_image, 0, 0, width, height);
		//Scan ROI.labeled_image 
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				int index = y*(width) + x;
				if(Util.getRGBavg(image[index]) == this.Mass_ID)
				{ area++;
				sum += Util.getRGBavg(orig_image[index]);
				}
			}
		}

	    if(this.extract_Area() != 0)
			   this.Average_Grey.value = sum/this.Area.value;
		else
			this.Average_Grey.value = -1;
		return this.Average_Grey.value;
	}

	public float extract_VarianceGrey()
	{
		int width = roi.getWidth();
		int height = roi.getHeight();
		int area = 0;
		float sum = 0;
		float temp = 0;
		int[] image = (roi.getLabeledImage()).allocPixels();
		(roi.getLabeledImage()).getPixels(image, 0, 0, width, height);
		int[] orig_image = (roi.getOriginalImage()).allocPixels();
		(roi.getOriginalImage()).getPixels(orig_image, 0, 0, width, height);
		//Scan ROI.labeled_image 
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				int index = y*(width) + x;
				if(Util.getRGBavg(image[index]) == this.Mass_ID)
				{ area++;
				  temp = Util.getRGBavg(orig_image[index]) - this.Average_Grey.value;
				  sum = temp*temp;
				}
			}

			// variance = (sum_sqr - sum*mean)/(n - 1)
			// See http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Algorithm_I
			if(area == 0)
			 this.Variance_Grey.value = -1.0f;
			else
			 this.Variance_Grey.value = sum/area;

		}
		return Variance_Grey.value;
	}


	public Vector<Point> detectBoundary(){

                //reinitially this.Boundary_Pixels to empty
		this.Boundary_Pixels = new Vector<Point>();

		//size of image
		int width = roi.getWidth();
		int height = roi.getHeight();

		//scan labeled image and at each point labeled this.MassID see if it is a boundary point and
		// if so put it in the vector.

		int[] image = (roi.getLabeledImage()).allocPixels();
		(roi.getLabeledImage()).getPixels(image, 0, 0, width, height);
		//Scan ROI.labeled_image
		//test to see if this is a Board pixel or not
		boolean isBorder = false;
		int count = 0;

		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{

				isBorder = false;

				//if we are not on a pixel with this Mass_ID skip it
				int index = y*(width) + x;
				if(Util.getRGBavg(image[index]) != this.Mass_ID)
					continue;

				//if we are at a image boundary automatically label it
				//a mass boundary
				if(x==0 || x==width-1 || y==0 || y==height-1){
					this.Boundary_Pixels.add(new Point(x,y));
					count++;
					continue;
				}

				//look at the pixel at location [x-1][y+1]
				index = (y+1)*(width) + (x-1);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x][y+1]
				index = (y+1)*(width) + (x);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x+1][y+1] 
				index = (y+1)*(width) + (x+1);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x-1][y]
				index = (y)*(width) + (x-1);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x+1][y]
				index = (y)*(width) + (x+1);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x-1][y-1]
				index = (y-1)*(width) + (x-1);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x][y-1]
				index = (y-1)*(width) + (x);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				//look at the pixel at location [x+1][y-1]
				index = (y-1)*(width) + (x+1);
				if (Util.getRGBavg(image[index]) != this.Mass_ID)
					isBorder = true;
				
				if (isBorder){
					Point p = new Point(x,y);
					this.Boundary_Pixels.add(p);
					count++;
	
				}


			}


		}

	System.out.println("Mass " + this.Mass_ID + " has " + count +" # Boundary Pixels.");
		return this.Boundary_Pixels;
	}



	public float extract_BoundaryLength() {

		this.detectBoundary();             
		this.Boundary_Length.value = this.Boundary_Pixels.size();
		return this.Boundary_Length.value;
	}


	public float extract_Sharpness()
	{


		this.Sharpness.value = this.Boundary_Length.value / this.Area.value;  
		return this.Sharpness.value;


	}

	public float extract_Thinness()
	{	


		this.thinness.value = (float) (this.Boundary_Length.value * this.Boundary_Length.value / (4*this.Area.value - 4*Math.PI));
		return this.thinness.value;	

	}

	public void detectALLFeatures() {

		//steps through method calls to get all features
            this.detectBoundary();
           this.extract_BoundaryLength();
		this.extract_Area();
		this.extract_AveGrey();
		this.extract_VarianceGrey();
		this.extract_Sharpness();
		this.extract_Thinness();

		System.out.println(this.toString());
		
		

	}

      public String toString(){

  		String s= "MASS  " + this.Mass_ID + " :\n";
            s += "Area: " + this.Area.value + " \n";
            s += "AveGrey: " + this.Average_Grey.value+ " \n";
	      s += "Boundary Length:" + this.Boundary_Length.value + " \n";
            s += "Variance Grey :" + this.Variance_Grey.value+ " \n";
            s += "Sharpness: " + this.Sharpness.value+ " \n";
            s += "Thinness: " + this.thinness.value+ " \n";
            return s;

        }


	//// Debug stuff
	public void dump_BoundingBox()
	{
		p.print("Min_row: " + Min_row);
		p.print("Min_col: " + Min_col);
		p.print("Max_row: " + Max_row);
		p.print("Max_col: " + Max_col);
	}
}
